package cn.echo.ecc.service.impl;

import cn.echo.ecc.domain.Course;
import cn.echo.ecc.domain.CourseMarket;
import cn.echo.ecc.domain.KillActivity;
import cn.echo.ecc.domain.KillCourse;
import cn.echo.ecc.dto.KillCourseOrderDto;
import cn.echo.ecc.dto.KillPreOrderDto;
import cn.echo.ecc.enums.GlobalExceptionCode;
import cn.echo.ecc.feign.CourseService;
import cn.echo.ecc.mapper.KillCourseMapper;
import cn.echo.ecc.result.JSONResult;
import cn.echo.ecc.service.IKillActivityService;
import cn.echo.ecc.service.IKillCourseService;
import cn.echo.ecc.util.AssertUtil;
import cn.echo.ecc.util.CodeGenerateUtils;
import cn.echo.ecc.vo.CourseInfoVo;
import cn.echo.ecc.vo.CourseOrderVo;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.mapper.EntityWrapper;
import com.baomidou.mybatisplus.mapper.Wrapper;
import com.baomidou.mybatisplus.service.impl.ServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RSemaphore;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author hulan
 * @since 2022-10-20
 */
@Service
@Slf4j
public class KillCourseServiceImpl extends ServiceImpl<KillCourseMapper, KillCourse> implements IKillCourseService {


    @Autowired
    private CourseService courseService;

    @Autowired
    private IKillActivityService killActivityService;

    @Autowired
    private RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Override
    public void joinKill(KillCourse killCourse) {
        Long courseId = killCourse.getCourseId();
        Long activityId = killCourse.getActivityId();
        // 1.使用JSR303进行参数校验
        // 2.校验课程是否存在，课程是否上架
        JSONResult jsonResult = courseService.get(courseId);
        AssertUtil.isTrue(jsonResult.isSuccess(), GlobalExceptionCode.SERVICE_ERROR);
        Object data = jsonResult.getData();
        AssertUtil.isNotNull(data,GlobalExceptionCode.COURSE_IS_NULL_ERROR);
        Course course = JSON.parseObject(JSON.toJSONString(data), Course.class);
        boolean courseState = Course.COURSE_STATUS_ONLINE == course.getStatus();
        AssertUtil.isTrue(courseState,GlobalExceptionCode.COURSE_IS_OFFLINE_ERROR);
        // 3.校验秒杀活动是否存在，活动状态是否未发布
        KillActivity killActivity = killActivityService.selectById(activityId);
        AssertUtil.isNotNull(killActivity,GlobalExceptionCode.KILL_ACTIVITY_IS_NULL_ERROR);
        boolean activityState = KillActivity.WAIT_STATE == killActivity.getPublishStatus();
        AssertUtil.isTrue(activityState,GlobalExceptionCode.KILL_ACTIVITY_STATE_ERROR);
        // 4.校验该秒杀课程在该秒杀活动中是否已经存在
        KillCourse killCourseTem = selectKillCourseByActivityIdAndCourseId(activityId,courseId);
        // 5.初始化秒杀课程未设置的值
        initKillCourse(killCourse,killActivity);
        // 6.新增
        insert(killCourse);
    }

    /**
     * 根据秒杀活动查询所有秒杀课程
     * @param id
     * @return
     */
    @Override
    public List<KillCourse> selectKillCourseByActivityId(Long id) {
        Wrapper<KillCourse> wrapper = new EntityWrapper<>();
        wrapper.eq("activity_id",id);
        return selectList(wrapper);
    }

    /**
     * 查询所有发布中的秒杀课程
     * @return
     */
    @Override
    public List<KillCourse> onlineAll() {
        Set<Object> keys = redisTemplate.keys("activity:*");
        List<KillCourse> killCourses = new ArrayList<>();
        keys.forEach(key -> {
            List values = redisTemplate.opsForHash().values(key);
            killCourses.addAll(values);
        });
        return killCourses;
    }

    /**
     * 根据秒杀活动id和秒杀课程id从redis中查询秒杀课程
     * @param killActivityId
     * @param killCourseId
     * @return
     */
    @Override
    public KillCourse onlineOne(Long killActivityId, Long killCourseId) {
        return (KillCourse)redisTemplate.opsForHash().get("activity:" + killActivityId,killCourseId.toString());
    }

    /**
     * 秒杀
     * @param killCourseOrderDto
     * @return
     */
    @Override
    public String kill(KillCourseOrderDto killCourseOrderDto) {
        Date now = new Date();
        Long userId = 4L;
        Long killActivityId = killCourseOrderDto.getKillActivityId();
        Long killCourseId = killCourseOrderDto.getKillCourseId();
        // 1.参数校验使用JSR303
        // 2.校验redis中是否有该秒杀课程
        Object redisKillCourse = redisTemplate.opsForHash().get("activity:" + killActivityId, killCourseId.toString());
        AssertUtil.isNotNull(redisKillCourse,GlobalExceptionCode.KILL_COURSE_IS_NULL_ERROR);
        // 3.校验活动状态是否正在秒杀中
        KillCourse killCourse = JSON.parseObject(JSON.toJSONString(redisKillCourse), KillCourse.class);
        AssertUtil.isTrue(killCourse.getKilling(),GlobalExceptionCode.KILL_IS_CANCEL_ERROR);
        // 4.校验每个用户一个活动中的一个商品只能下单一次
        Object redisOrderNo = redisTemplate.opsForValue().get(userId + ":" + killActivityId + ":" + killCourseId);
        AssertUtil.isNull(redisOrderNo,GlobalExceptionCode.KILL_ORDER_ERROR);
        // 5.扣减信号量,判断是否扣减成功
        RSemaphore semaphore = redissonClient.getSemaphore(killActivityId + ":" + killCourseId);
        boolean tryAcquire = semaphore.tryAcquire(1);
        AssertUtil.isTrue(tryAcquire,GlobalExceptionCode.KILL_ERROR);
        // 6.创建预创订单
        KillPreOrderDto killPreOrderDto = new KillPreOrderDto();
        killPreOrderDto.setActivityId(killActivityId);
        killPreOrderDto.setCourseId(killCourse.getCourseId());
        killPreOrderDto.setCourseName(killCourse.getCourseName());
        killPreOrderDto.setCoursePic(killCourse.getCoursePic());
        killPreOrderDto.setKillCount(killCourse.getKillCount());
        killPreOrderDto.setKillLimit(killCourse.getKillLimit());
        killPreOrderDto.setKillPrice(killCourse.getKillPrice());
        String orderNo = CodeGenerateUtils.generateOrderSn(userId);
        killPreOrderDto.setOrderNo(orderNo);
        killPreOrderDto.setKillCourseId(killCourseId);
        // 7.将预创订单存入redis
        redisTemplate.opsForValue().set(orderNo,killPreOrderDto);
        // 8.存一个用户秒杀成功的标识
        redisTemplate.opsForValue().set(userId + ":" + killActivityId + ":" + killCourseId,orderNo);
        // 9.打印日志秒杀消耗时间
        log.info("秒杀耗时：{}",System.currentTimeMillis() - now.getTime());
        return orderNo;
    }

    /**
     * 根据预创订单号查询课程信息
     * @param orderNo
     * @return
     */
    @Override
    public CourseOrderVo oneByOrder(String orderNo) {
        // 1.根据预创订单号从redis中查询预创订单
        Object redisKillPreOrderDto = redisTemplate.opsForValue().get(orderNo);
        AssertUtil.isNotNull(redisKillPreOrderDto,GlobalExceptionCode.KILL_ORDER_NO_ERROR);
        KillPreOrderDto killPreOrderDto = JSON.parseObject(JSON.toJSONString(redisKillPreOrderDto), KillPreOrderDto.class);
        // 2.查询数据，封装CourseOrderVo
        List<CourseInfoVo> courseInfoVos = new ArrayList<>();
        CourseOrderVo courseOrderVo = new CourseOrderVo();
        CourseInfoVo courseInfoVo = new CourseInfoVo();
        Course course = new Course();
        course.setName(killPreOrderDto.getCourseName());
        course.setPic(killPreOrderDto.getCoursePic());
        CourseMarket courseMarket = new CourseMarket();
        courseMarket.setPrice(killPreOrderDto.getKillPrice());
        courseInfoVo.setCourseMarket(courseMarket);
        courseInfoVo.setCourse(course);
        courseInfoVos.add(courseInfoVo);
        courseOrderVo.setTotalAmount(courseMarket.getPrice());
        courseOrderVo.setCourseInfos(courseInfoVos);
        return courseOrderVo;
    }

    /**
     * 初始化秒杀课程未设置的值
     * @param killCourse
     * @param killActivity
     */
    private void initKillCourse(KillCourse killCourse, KillActivity killActivity) {
        killCourse.setStartTime(killActivity.getStartTime());
        killCourse.setEndTime(killActivity.getEndTime());
        killCourse.setTimeStr(killActivity.getTimeStr());
        killCourse.setPublishStatus(killActivity.getPublishStatus());
        killCourse.setCreateTime(new Date());
    }

    /**
     * 根据秒杀活动id和课程id查询秒杀课程
     * @param activityId
     * @param courseId
     * @return
     */
    private KillCourse selectKillCourseByActivityIdAndCourseId(Long activityId, Long courseId) {
        Wrapper<KillCourse> wrapper = new EntityWrapper<>();
        wrapper.eq("activity_id",activityId).eq("course_id",courseId);
        return selectOne(wrapper);
    }
}
